<?php

/***  DOCUMENTATION LAYER

Greqo Abstract Class

Name: Greqo
Version: 0.9
Last Update: July 2007
Author: Tom at klenwell@gmail.com

DESCRIPTION
  This is the base class that all Greqo Google service should inherit from.

METHODS
  MAGIC
  Greqo($debug=0)  *php 4 constructor*
  __construct($debug)    *php 5 constructor*
  __destruct()  
  
  PUBLIC
  request_clientlogin_token($service, $force=0)
  atom_to_array($xml_string)
  load_magpie()
  load_cacher($lifetime=3600)
  load_mailer()
  escape_entities($string)
  clean_cache()
  print_d($message, $color='c33')
  print_r()
  dump()
  
  PRIVATE
  _reset_client()
  _send_client_request()
  _parse_auth_response($body='', $show_google_error=1)
  _set_auth_token($token)
  _is_login_prepared()
  _is_auth_request_prepared()
  _trigger_client_error()
  _trigger_request_error($message)
  _set_auth_token($token)
  _set_session_data()
  _get_session_data()
  _has_session_data()
  _set_filename()
  _set_dirpath()
  
USAGE
  Intended as a base (abstract) class
  
  // authorization using GreqoBlogger extension
  $GreqoBlogger = new GreqoBlogger();
  $GreqoBlogger->email = 'yourname@gmail.com';
  $GreqoBlogger->password = '*******';
  if ( !$auth_token = $GreqoBlogger->request_clientlogin_token($service='blogger', $force=1) ) 
  {
    die('unable to get auth token');
  }

NOTES
  This class is designed to be compatible with PHP 4.  Disable strict error
  notices in PHP 5 or above.
  
  SESSION values
    $_SESSION['GDATA']['auth_token']
    
______________________________________________________________________________*/


// Greqo
/*____________________________________________________________________________*/
class Greqo
{
/* PUBLIC PROPERTIES */
var $debug = 0;
var $class_name = __CLASS__;
var $oid = 'Greqo';
var $DS = DIRECTORY_SEPARATOR;

// client info
var $client_id = 'Klenwell-Greqo-0.9';
var $GreqoClient = 0;      /* PEAR request object */
var $GreqoResponse = 0;    /* PEAR response object (in response to GreqoClient request */
var $GreqoMailer = 0;      /* PHPMailer object */
var $GreqoCacher = 0;      /* PEAR cache lite object */
var $email = '';
var $password = '';
var $gdata_auth_key = 'Auth';
var $gdata_auth_token = '';
var $service = '';
var $allow_redirect = 1;    // number of redirect attempts allowed
var $redirect_attempts = 0;
var $is_redirected = 0;

// paths
var $root = '';
var $pear_root = '';
var $pear_driver = '';
var $magpie_root = '';
var $phpmailer_root = '';
var $phpmailer_driver = '';
var $cacher_root = '';
var $cacher_driver = '';
var $cache_dir = '';

// mailer settings
var $mail_From = '';    // email address
var $mail_FromName = 'Greqo Mailer';
var $mail_To = '';      // email address
var $mail_ToName = 'Greqo Admin';

// cache settings
var $auto_clean_cache_dir = 1;
var $auto_clean_cache_ratio = 25;

// some magic urls (scopes)
var $url_clientlogin = 'https://www.google.com/accounts/ClientLogin';

/* PRIVATE PROPERTIES */
var $_filename = '';
var $_dirpath = '';


/* ** MAGIC METHODS ** */
// php4 constructor
function Greqo($debug=0, $oid=null)
{
  $this->__construct($debug, $oid);
  register_shutdown_function( array($this, '__destruct') );
}
// END constructor

// php5 constructor
function __construct($debug=0, $oid=null)
{
  // default
  $this->debug = $debug;
  $this->oid = ( empty($oid) ) ? $this->class_name : $oid;
  $this->_set_filename();
  $this->_set_dirpath();
  
  // set path values
  $this->root = $this->_dirpath;
  $this->pear_root = $this->root . 'PEAR_core' . $this->DS;
  $this->pear_driver = $this->pear_root . '_driver.inc.php';
  $this->magpie_root = $this->root . 'magpierss' . $this->DS;
  $this->phpmailer_root = $this->root . 'phpmailer' . $this->DS;
  $this->phpmailer_driver = $this->phpmailer_root . 'class.phpmailer.php';
  $this->cacher_root = $this->root . 'pear_cache_lite' . $this->DS;
  $this->cacher_driver = $this->cacher_root . 'Lite.php';
  $this->cache_dir = $this->root . 'cache' . $this->DS;
  
  // constants
  define('MAGPIE_CACHE_DIR', $this->cache_dir);
  define('CACHE_DIR', $this->cache_dir);
  
  // dependant files
  require_once($this->pear_driver);
  require_once($this->pear_root . 'HTTP/Request.php');
  
  // start session
  if ( !session_id() ) session_start(); 
  
  // load PEAR HTTP for $GreqoClient
  $this->GreqoClient =& new HTTP_Request();
  $this->_reset_client();
  if ( $this->debug ) $this->print_d('GreqoClient (HTTP_Request object) created'); 
  
  // debug
  if ( $this->debug ) $this->print_d('debugging is active for oid ' . $this->oid);
  if ( $this->debug ) $this->print_d('constructor complete for class ' . __CLASS__);
}
// END constructor

// destructor
function __destruct()
{
  if ( $this->debug ) $this->print_d("destroying class {$this->class_name} (oid: {$this->oid})");
}
// END destructor



/* ** PUBLIC METHODS ** */
// method: request clientlogin token
function request_clientlogin_token($service='', $force=0)
{
  $GDATA['token'] = 0;    // returns token and stores in session
  
  // set service
  $this->service = ( !empty($service) ) ? $service : $this->service;
  
  if ( $this->debug ) $this->print_d('requesting auth token for service ' . $this->service);
  
  // is request prepared
  if ( !$this->_is_auth_request_prepared() ) return 0;
  
  // Prime Session value
  $_SESSION['GDATA']['auth_token'] = ( !empty($_SESSION['GDATA']['auth_token']) ) ? $_SESSION['GDATA']['auth_token'] : 0;

  // Do we have a token already?
  #trigger_error("{$_SESSION['GDATA']['auth_token']} && $force"); die;
  if ( !empty($_SESSION['GDATA']['auth_token']) && !$force ) 
  {
    if ( $this->debug ) $this->print_d("we have a stored token and force=0, so we return that: {$_SESSION['GDATA']['auth_token']}", 'blue');
    $this->_set_auth_token($_SESSION['GDATA']['auth_token']);    
    return $this->gdata_auth_token;
  }
  
  // Prepare GreqoClient
  $this->GreqoClient->setURL($this->url_clientlogin);
  $this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_POST);
  $this->GreqoClient->addHeader('Content-type', 'application/x-www-form-urlencoded');
  $this->GreqoClient->addPostData('accountType', 'HOSTED_OR_GOOGLE');
  $this->GreqoClient->addPostData('Email', $this->email);
  $this->GreqoClient->addPostData('Passwd', $this->password);
  $this->GreqoClient->addPostData('service', $service);
  $this->GreqoClient->addPostData('source', $this->client_id);

  // Send Request
  if ( !$this->_send_client_request() ) return 0;
  
  // Process Results
  $GDATA = $this->_parse_auth_response($this->GreqoResponse->body, 0);
  
  // success
  if ( $this->GreqoResponse->code_class == 2 )
  {
    if ( $this->debug ) $this->print_d("token successfully retrieved: {$GDATA['token']}", 'green'); 
  }
  // failed
  elseif ( $this->GreqoResponse->code_class == 4 )
  {
    if ( $this->debug ) $this->print_d('<h4>Google Response</h4>' . $this->print_r($GDATA));
    trigger_error('request failed: 400 response', E_USER_WARNING);
    return 0;
  }
  
  // Save token to session
  if ( !empty($GDATA['token']) ) $this->_set_auth_token($GDATA['token']);
  
  return $GDATA['token'];
}
// END method

// method: atom to array
function atom_to_array($xml_string)
{
  $ARRAY = array();    // return

  $this->load_magpie();
  
  $MagpieParser = new MagpieRSS($xml_string);
  if ( $this->debug ) define('MAGPIE_DEBUG', 1);
  if ( $this->debug ) $this->print_d('MagpieRSS object loaded');
  
  // dump in array
  if ( $MagpieParser && !$MagpieParser->ERROR )
  {
    $xml = print_r($MagpieParser, 1); 
    if ( $this->debug ) $this->print_d("<pre>$xml</pre>");
    foreach ( $MagpieParser->channel as $_ARR ) $ARRAY['CHANNEL'][] = $_ARR;
    foreach ( $MagpieParser->items as $_ARR ) $ARRAY['ITEM'][] = $_ARR;
  }
    
  // error
  else
  {
    trigger_error("Error: " . $rss->MagpieParser, E_USER_WARNING);
  }

  return $ARRAY;
}
// END method

// method: load magpie
function load_magpie()
{
  static $magpie_is_loaded = 0;
  
  // loaded?
  if ( $magpie_is_loaded ) return;

  require_once($this->magpie_root . 'rss_fetch.inc');
  $magpie_is_loaded = 1;
  return;
}
// END method

// method: load cache
function load_cacher($lifetime=3600)
{
  // already loaded
  if ( is_object($this->GreqoCacher) ) 
  {
    $this->GreqoCacher->setOption('lifeTime', $lifetime);
    return;
  }
  
  // config option
  $_CONFIG = array
  (
    'cacheDir' => CACHE_DIR,
    'lifeTime' => $lifetime,
    'automaticSerialization' => FALSE,
  );

  // load
  require_once($this->cacher_driver);
  $this->GreqoCacher = new Cache_Lite($_CONFIG);
  
  if ( $this->debug ) $this->print_d('GreqoCacher loaded');
  if ( $this->debug ) $this->GreqoCacher->setToDebug();
  
  // occasionally clean up cache dir
  if ( $this->auto_clean_cache_dir && time() % $this->auto_clean_cache_ratio == 1 )
  {
    if ( $this->debug ) $this->print_d('auto-cleaning cache dir');
    $this->clean_cache();
  }
  
  return;
}
// END method

// method: load mailer
function load_mailer()
{
  // already loaded
  if ( is_object($this->GreqoMailer) ) return;

  // load
  require_once($this->phpmailer_driver);
  $this->GreqoMailer = new PHPMailer();
  return;
}
// END method

// method: escape_entities
function escape_entities($string)
{
  $escaped_string = '';    // return

  // escape
  $escaped_string = htmlspecialchars($string, ENT_QUOTES);
  $escaped_string = preg_replace('/&#0*39;/', '&apos;', $escaped_string);

  return $escaped_string;
}
// END method

// method: clean cache (see http://pear.php.net/manual/en/package.caching.cache-lite.cache-lite.clean.php)
function clean_cache()
{
  $cleaned = false;    // return
  if ( $this->debug ) $this->print_d('attempting to clean cache');
  if ( !$this->GreqoCacher )
  {
    if ( $this->debug ) $this->print_d('no cache object -- return false');
    return $cleaned;
  }
  if ( $this->GreqoCacher->clean(false, 'old') ) $cleaned = true;
  return $cleaned;
}
// END method

// method: print_d
function print_d($message, $color='c33')
{
  $out = "<div style='line-height:1.5em; font-family:monospace; color:$color;'>$message</div>";
  echo $out;
  return;
}
// END method

// method: print_r
function print_r($Mixed)
{
  $return = htmlspecialchars(print_r($Mixed, 1));
  $return = "<pre>$return</pre>";
  return $return;
}
// END method

// method: dump
function dump()
{
  echo $this->print_r($this);
  return;
}
// END method



/* ** PRIVATE METHODS ** */
// method: reset client
function _reset_client()
{
  // PEAR docs advise calling the constructor to reset the client
  if ( $this->debug ) $this->print_d('resetting client');
  $_CONFIG = array( 'timeout' => 30 );
  $this->GreqoClient->HTTP_Request('', $_CONFIG);
  $this->redirect_attempts = 0;
}

// method: send client request
function _send_client_request()
{
  // are we processing a redirect request?
  if ( $this->is_redirected )
  {
    $this->is_redirected = 0;
    $this->GreqoClient->setURL($this->redirect_url);
  }
  
  if ( $this->debug ) $this->print_d('<b>sending client request to ' . $this->GreqoClient->_url->url . '</b>', 'blue');

  // send request
  $this->GreqoResponse = $this->GreqoClient->sendRequest();
  
  // error check
  if ( PEAR::isError($this->GreqoResponse) )
  {
    $this->_trigger_client_error('PEAR error on request');  
    return 0;
  }
  
  // process response
  if ( !is_object($this->GreqoResponse) ) $this->GreqoResponse = new stdClass();
  $this->GreqoResponse->code = $this->GreqoClient->getResponseCode();
  $this->GreqoResponse->code_class = floor($this->GreqoResponse->code / 100);
  $this->GreqoResponse->body = $this->GreqoClient->getResponseBody();
  $this->GreqoResponse->HEADERS = $this->GreqoClient->getResponseHeader();
  if ( $this->debug ) $this->print_d($this->print_r($this->GreqoResponse));
  
  // redirect code (redirects allowed)
  if ( $this->GreqoResponse->code_class == 3 && $this->allow_redirect )
  {
    $this->allow_redirect--;
    if ( $this->redirect_url = $this->GreqoClient->getResponseHeader('Location') )
    {
      $this->is_redirected = 1;
      $this->redirect_attempts++;
      if ( $this->debug ) $this->print_d("Redirect Code {$this->GreqoResponse->code} - Redirecting to: {$this->redirect_url}");
      $this->_send_client_request();
    }
    else
    {
      trigger_error("Redirect Code {$this->GreqoResponse->code}: Location header not found", E_USER_WARNING);
      return 0;
    }
  }
  // redirect code: (redirects not allowed)
  elseif ( $this->GreqoResponse->code_class == 3 )
  {
    trigger_error("Redirect Code {$this->GreqoResponse->code}: redirect limit exceeded ({$this->redirect_attempts} redirect attempted)", E_USER_WARNING);
  }
  
  // reset client
  $this->_reset_client();
  
  return 1;
}
// END method

// method: parse auth response
function _parse_auth_response($body='', $show_google_error=1)
{
  $GDATA = array();    // return

  // Sanity Check
  if ( empty($body) )
  {
    trigger_error('empty response from Google', E_USER_WARNING);
    return 0;
  }
  
  // Parse Response
  $_LINE = explode("\n", $body);
  foreach ( $_LINE as $_string )
  {
    $_string = trim($_string);
    if ( !strpos($_string, '=') ) continue;
    
    $_PARAM = explode('=', $_string);
    $GDATA[$_PARAM[0]] = $_PARAM[1];
  }
  
  // Exception check
  if ( empty($GDATA) )
  {
    trigger_error('no GData values found', E_USER_WARNING);
    return 0;
  }
  elseif ( !empty($GDATA['Error']) && $show_google_error )
  {
    trigger_error("Google Error Response: {$GDATA['Error']}", E_USER_NOTICE);
  }
  
  // Set Auth as Token
  if ( $_PARAM[0] == $this->gdata_auth_key ) $GDATA['token'] = $_PARAM[1];

  
// *** RETURN

  return $GDATA;
}
// END method

// method: _set_auth_token
function _set_auth_token($token)
{
  $this->gdata_auth_token = $token;
  $_SESSION['GDATA']['auth_token'] = $token;
  if ( $this->debug ) $this->print_d('$_SESSION[GDATA][auth_token] set to ' . $_SESSION['GDATA']['auth_token']);
}
// END method

// method: is login prepared
function _is_login_prepared()
{
  $is_prepared = 0;    // return
  
  if ( empty($this->email) )
  {
    $this->_trigger_request_error('email/login must be set');
    return 0;
  }
  
  if ( empty($this->password) )
  {
    $this->_trigger_request_error('password must be set');
    return 0;
  }
  
  return 1;
}
// END method

// method: is auth request prepared
function _is_auth_request_prepared()
{
  $is_prepared = 0;    // return
  
  if ( !$this->_is_login_prepared() ) return 0;
  
  if ( empty($this->service) )
  {
    $this->_trigger_request_error('service must be set');
    return 0;
  }
  
  return 1;
}
// END method

// method: trigger client error
function _trigger_client_error() 
{
  $this->GreqoClient->_postData['Passwd'] = $this->password = '**scrubbed**';
  trigger_error('GreqoClient error: ' . $this->GreqoResponse->getMessage(), E_USER_WARNING);    
  if ( $this->debug ) 
  {
    $this->print_d('<h4>Result Object</h4>' . $this->print_r($this->GreqoResponse), 'magenta');
    $this->print_d('<h4>GreqoClient Object</h4>' . $this->print_r($this->GreqoClient), 'orangered');
  }  
}
// END method

// method: trigger request error
function _trigger_request_error($message) 
{
  $this->GreqoClient->_postData['Passwd'] = $this->password = '**scrubbed**';
  trigger_error('request error: ' . $message, E_USER_WARNING);    
  if ( $this->debug ) 
  {
    $this->print_d('<h4>Result Object</h4>' . $this->print_r($this->GreqoResponse), 'magenta');
    $this->print_d('<h4>GreqoClient Object</h4>' . $this->print_r($this->GreqoClient), 'orangered');
  }  
}
// END method

// method: _set_session_data
function _set_session_data()
{
  // initialize session
  $_SESSION[$this->oid] = array();
  
  // add session data here
  
  return;
}
// END method

// method: get session data
function _get_session_data()
{
  // initialize session
  if ( !$this->_has_session_data() ) return; 
    
  // retrieve session variables
  // $this->var = $_SESSION[$this->oid]['var'];
  
  return;
}
// END method

// method: has session data
function _has_session_data()
{
  // initialize session
  if ( !session_id() ) session_start(); 
    
  // retrieve session variables
  if ( empty($_SESSION[$this->oid]) ) return 0;
  else return 1;
}
// END method

function _set_filename() { $this->_filename = basename(__FILE__); }
function _set_dirpath() { $this->_dirpath = dirname(__FILE__) . $this->DS; }

} // end class
/*____________________________________________________________________________*/

?>
